package com.krolik22.maven.repgen.report;

import org.apache.maven.artifact.Artifact;
import org.apache.maven.artifact.factory.ArtifactFactory;
import org.apache.maven.artifact.metadata.ArtifactMetadataSource;
import org.apache.maven.artifact.repository.ArtifactRepository;
import org.apache.maven.artifact.resolver.ArtifactCollector;
import org.apache.maven.artifact.resolver.filter.ArtifactFilter;
import org.apache.maven.artifact.resolver.filter.ScopeArtifactFilter;
import org.apache.maven.doxia.sink.Sink;
import org.apache.maven.doxia.siterenderer.Renderer;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.project.MavenProject;
import org.apache.maven.reporting.AbstractMavenReport;
import org.apache.maven.reporting.MavenReportException;
import org.apache.maven.shared.artifact.filter.StrictPatternExcludesArtifactFilter;
import org.apache.maven.shared.artifact.filter.StrictPatternIncludesArtifactFilter;
import org.apache.maven.shared.dependency.tree.DependencyNode;
import org.apache.maven.shared.dependency.tree.DependencyTreeBuilder;
import org.apache.maven.shared.dependency.tree.DependencyTreeBuilderException;
import org.apache.maven.shared.dependency.tree.filter.AndDependencyNodeFilter;
import org.apache.maven.shared.dependency.tree.filter.ArtifactDependencyNodeFilter;
import org.apache.maven.shared.dependency.tree.filter.DependencyNodeFilter;

import java.io.File;
import java.io.FileNotFoundException;
import java.util.*;

/**
 * @goal graph
 * @phase site
 */
public class Graph extends AbstractMavenReport {
    /**
     * The enclosing project.
     *
     * @parameter expression="${project}"
     * @required
     * @readonly
     */
    protected MavenProject project;

    /**
     * The projects in the reactor for aggregation report. <p/> Note: This is
     * passed by Maven and must not be configured by the user. </p>
     *
     * @parameter expression="${reactorProjects}"
     * @readonly
     */
    private List<MavenProject> reactorProjects;

    /**
     * The artifact factory to use.
     *
     * @component
     * @required
     * @readonly
     */
    private ArtifactFactory artifactFactory;

    /**
     * The artifact metadata source to use.
     *
     * @component
     * @required
     * @readonly
     */
    private ArtifactMetadataSource artifactMetadataSource;

    /**
     * The artifact collector to use.
     *
     * @component
     * @required
     * @readonly
     */
    private ArtifactCollector artifactCollector;


    /**
     * The dependency tree builder to use.
     *
     * @component
     * @required
     * @readonly
     */
    private DependencyTreeBuilder dependencyTreeBuilder;


    /**
     * The artifact repository to use.
     *
     * @parameter expression="${localRepository}"
     * @required
     * @readonly
     */
    private ArtifactRepository localRepository;

    /**
     * @parameter default-value="${project.remoteArtifactRepositories}"
     * @required
     * @readonly
     */
    private List<?> remoteRepositories;

    /**
     * @parameter
     * @see org.apache.maven.shared.artifact.filter.StrictPatternExcludesArtifactFilter
     */
    private List<String> excludes;

    /**
     * @parameter
     * @see org.apache.maven.shared.artifact.filter.StrictPatternIncludesArtifactFilter
     */
    private List<String> includes;

    /**
     * @parameter
     */
    private ArrayList<ClusterGraph> clusters;

    /**
     * Directory where reports will go.
     *
     * @parameter expression="${project.reporting.outputDirectory}"
     * @required
     * @readonly
     */
    private String outputDirectory;

    /**
     * @parameter
     * @readonly
     */
    private String reactorProject;

    /**
     * Generates the site report.
     *
     * @component
     */
    private Renderer siteRenderer;

    /**
     * @parameter
     */
    private List<String> scopes;

    /**
     * @parameter default-value="false"
     */
    private Boolean hideOptional;

    /**
     * @parameter default-value="false"
     */
    private Boolean hideOmitted;

    /**
     * @parameter default-value="false"
     */
    private Boolean hideVersion;

    /**
     * @parameter default-value="false"
     */
    private Boolean hideGroupId;

    /**
     * @parameter default-value="false"
     */
    private Boolean hideType;

    /**
     * @parameter default-value="false"
     */
    private Boolean hideTransitive;

    /**
     * If set to true then the intermediate dot file will not be deleted.
     * <br/>
     * @parameter default-value="true"
     */
    protected boolean keepDot;

    /**
     * @parameter default-value="Dependency Graph for ${project.name}"
     */
    private String label;

    /**
     * If true then the 'test scope' and 'optional' attributes are cascaded
     * down to the dependencies of the original node.
     * <p/>
     * <br/>
     *
     * @parameter default-value="true"
     */
    protected boolean cascade;

    /**
     * The direction that the graph will be laid out in.
     * it can be one of the following values:
     * <br/>
     * <code>TB LR BT RL <code>
     * <br/>
     * top to bottom, from left to right, from bottom to top, and from right to left, respectively
     * <p/>
     * <br/>
     *
     * @parameter default-value="TB"
     */
    protected String direction;

    private DependencyNodeFilter filter;


    protected Renderer getSiteRenderer() {
        return siteRenderer;
    }

    protected String getOutputDirectory() {
        return outputDirectory;
    }

    protected MavenProject getProject() {
        return project;
    }

    public String getOutputName() {
        return "graph";
    }

    public String getName(Locale locale) {
        return "Dependency Graph";
    }

    public String getDescription(Locale locale) {
        return "Graphical Dependency Overview of the Project";
    }


    @Override
    protected void executeReport(Locale locale) throws MavenReportException {
        try {
            Sink sink = getSink();
            sink.head();
            sink.title();
            sink.text(getName(locale));
            sink.title_();
            sink.head_();

            sink.body();
            sink.section1();

            sink.sectionTitle1();
            sink.text("Dependency Overview Graph for " + getProject().getName());
            sink.sectionTitle1_();
            sink.lineBreak();
            sink.lineBreak();
            // graph generation

            generateOverview();

            sink.figure();
            sink.figureGraphics("images/" + getOutputName() + ".png");
            sink.figureCaption();
            sink.text("Dependency Overview Graph");
            sink.figureCaption_();
            sink.figure_();
            sink.lineBreak();
            sink.section1_();
            sink.body_();
            sink.flush();
            sink.close();

        } catch (Exception e) {
            throw new MavenReportException("Execution error: " + e.getMessage(), e);
        }
    }

    private void generateOverview() throws DependencyTreeBuilderException, FileNotFoundException, MojoExecutionException {

        List<DependencyNodeFilter> filters = new ArrayList<DependencyNodeFilter>();
        // filter excludes
        if (excludes != null) {
            getLog().debug("+ Filtering dependency tree by artifact exclude patterns: " + excludes);
            ArtifactFilter artifactFilter = new StrictPatternExcludesArtifactFilter(excludes);
            filters.add(new ArtifactDependencyNodeFilter(artifactFilter));
        }
        // filter includes
        if (includes != null) {
            getLog().debug("+ Filtering dependency tree by artifact include patterns: " + includes);
            ArtifactFilter artifactFilter = new StrictPatternIncludesArtifactFilter(includes);
            filters.add(new ArtifactDependencyNodeFilter(artifactFilter));
        }
        if (filters.size() > 0) {
            filter = new AndDependencyNodeFilter(filters);
        }

        ArtifactFilter artifactFilter = new ScopeArtifactFilter(Artifact.SCOPE_COMPILE);

        DependencyVisualizer visualizer = new DependencyVisualizer();
        visualizer.filter = filter;
        visualizer.scopes = scopes;
        visualizer.cascade = cascade;
        visualizer.direction = direction;
        visualizer.hideOptional = hideOptional;
        visualizer.hideOmitted = hideOmitted;
        visualizer.hideVersion = hideVersion;
        visualizer.hideGroupId = hideGroupId;
        visualizer.hideType = hideType;
        visualizer.keepDot = false;
        visualizer.label = label;
        visualizer.hideTransitive = hideTransitive;
        visualizer.clustersMap = clusters;
        visualizer.keepDot = keepDot;

        visualizer.log = getLog();

        List<MavenProject> projects = new ArrayList<MavenProject>();
        projects.add(project);
        if (project.isExecutionRoot()) {
            projects.addAll(reactorProjects);
        }

        for (MavenProject reactorProject : projects) {
            DependencyNode dn = dependencyTreeBuilder.buildDependencyTree(
                    reactorProject, localRepository, artifactFactory, artifactMetadataSource,
                    artifactFilter, artifactCollector);
            visualizer.add(dn);
        }

        new File(outputDirectory + "/images").mkdirs();
        File dotFile = new File(outputDirectory + "/images/" + getOutputName() + ".png");
        visualizer.export(dotFile);

    }


}
